package org.emf.plugins.tomcatlauncher.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.emf.plugins.tomcatlauncher.server.KyahRequest.Command;

/**
 * Represents the communication channel between client and server.
 * It holds request and response messages as well as the protocol definition used to
 * take the communication forward.
 */
public class CommChannel {

    /** Server port. */
    public static final int SERVER_PORT = 4777;

    /** Indicates the end of a command or response message.*/
    static final String SLASH = "/";

    /** Regex used as a pattern to identify commands sent by the client.*/
    private static final String COMMAND_REGEX = "command:(.+?)" + SLASH;
    /** Regex used as a pattern to replace the command name in the {@link #COMMAND_REGEX}. */
    private static final String REPLACE_COMMAND_REGEX = "\\(\\.\\+\\?\\)";

    /** Indicates the end of protocol. */
    private static final String END = "END" + SLASH;

    /** Channel input. Reads commands/responses. */
    private BufferedReader inputChannel;
    /** Channel output. Writes commands/responses. */
    private PrintWriter outputChannel;

    /** Request object. */
    private KyahRequest request;

    /** Class constructor. */
    public CommChannel(BufferedReader inputChannel, PrintWriter outputChannel) {
        this.inputChannel = inputChannel;
        this.outputChannel = outputChannel;
    }

    /**
     * Reads a response sent by server.
     * @return the response object.
     * @throws IOException if it's not possible to read the response.
     */
    public KyahResponse readResponse() throws IOException {
        StringBuilder message = new StringBuilder();
        String inputLine = null;

        while ((inputLine = inputChannel.readLine()) != null) {
            if (END.equals(inputLine)) {
                break;
            }
            message.append(inputLine);
        }
        return parseResponse(message.toString());
    }

    /**
     * Sends a response back to the client.
     * @param response response object.
     */
    public void sendResponse(KyahResponse response) {
        outputChannel.println(response.getAsString());
        outputChannel.println(END);
        outputChannel.flush();
    }

    /**
     * Reads a command sent by client.
     * @return true if command was read successfully.
     * @throws IOException if command could not be read.
     */
    public boolean readCommand() throws IOException {
        StringBuilder message = new StringBuilder();
        String inputLine = null;

        while ((inputLine = inputChannel.readLine()) != null) {
            if (END.equals(inputLine)) {
                break;
            }
            message.append(inputLine);
        }
        request = new KyahRequest(parseCommand(message.toString()));
        return true;
    }

    /**
     * Sends a command to server.
     * @param command command to be sent.
     * @return the response as a result of the command being sent.
     * @throws IOException if was not possible to send the command.
     */
    public KyahResponse sendCommand(Command command) throws IOException {
        Pattern pattern = Pattern.compile(REPLACE_COMMAND_REGEX);
        Matcher matcher = pattern.matcher(COMMAND_REGEX);
        if (matcher.find()) {
            String commandAsString = matcher.replaceFirst(command.getName());
            outputChannel.println(commandAsString);
            outputChannel.println(END);
            outputChannel.flush();
        } else {
            throw new IllegalArgumentException("Invalid command");
        }
        return readResponse();
    }

    /**
     * Gets the request object.
     * @return the request object.
     */
    public KyahRequest getRequest() {
        return request;
    }

    /**
     * Closes the communication channel and messages are no longer supported.
     * @throws IOException if in/output channels could not be closed.
     */
    public void close() throws IOException {
        if (inputChannel != null) {
            inputChannel.close();
        }
        if (outputChannel != null) {
            outputChannel.close();
        }
    }

    /*
     * Parses the response coming out from server.
     * @param msg protocol message as a string.
     * @return the response object representing the message.
     */
    private static KyahResponse parseResponse(String msg) {
        Pattern pattern = Pattern.compile(KyahResponse.RESPONSE_REGEX);
        Matcher matcher = pattern.matcher(msg);
        if (matcher.find()) {
            return new KyahResponse(matcher.group(1), matcher.group(2));
        }
        return null;
    }

    /*
     * Parses the command to be send to the server.
     * returns null if the command was undefined.
     */
    private static Command parseCommand(String msg) {
        Pattern pattern = Pattern.compile(COMMAND_REGEX);
        Matcher matcher = pattern.matcher(msg);
        if (matcher.find()) {
            return Command.get(matcher.group(1));
        }
        return null;
    }
}
